package dao

import models.Tables._
import org.joda.time.DateTime
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.jdbc.JdbcProfile
import tool.Pojo.PageData

import javax.inject.Inject
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

class AbundanceScDao @Inject() (protected val dbConfigProvider: DatabaseConfigProvider)
    extends HasDatabaseConfigProvider[JdbcProfile] {

  import utils.MyPostgresProfile.api._

  type MyRow = AbundanceScRow
  val table = AbundanceSc

  def insert(row: MyRow) = {
    db.run(table += row)
  }

  def insertAll(rows: Seq[MyRow]) = db.run(
    table ++=
      rows
  )

  def deleteAll(): Future[Unit] = db.run(table.delete).map(_ => ())

  def getTrueColumnName(x: String) = {
    val r = "([A-Z])".r
    r.replaceAllIn(x, (matcher) => s"_${matcher.group(1).toLowerCase}")
  }

  def selectUniqueValues(keys: List[String]) = {
    val action = DBIO.sequence {
      val rs = keys.map { key =>
        val trueColumnName = getTrueColumnName(key)
        table
          .map { t =>
            t.getStrColumn(trueColumnName)
          }
          .distinct
          .result
      }
      rs
    }
    db.run(action)
  }

  def selectAll(phenotype: String) = {
    db.run(table.filter(_.phenotype === phenotype).result)
  }
  def selectAll = {
    db.run(table.result)
  }

  def selectAll(map: collection.Map[String, ujson.Value], pageData: PageData) = {
    val columnTrue = LiteralColumn(true)
    val searchJsons = map("search").arr
    val filterData = table
      .filter { x =>
        val textJsons = searchJsons.filter { j =>
          j.obj("kind").str == "text"
        }
        val bs = textJsons.map { j =>
          val columnName = j("name").str
          val trueColumnName = getTrueColumnName(columnName)
          val searchType = j("searchType").str
          val v = j("value").str
          val vs = v.split(";")
          val cV = x.getStrColumn(trueColumnName)
          vs.map { eachV =>
            searchType match {
              case "like" => cV.like(s"%${eachV}%")
              case "equals" => cV === s"${eachV}"
            }
          }.reduceLeft(_ || _)
        }
        val b = bs.foldLeft(true.bind)((x, y) => x && y)
        b
      }
      .filter { x =>
        val checkboxJsons = searchJsons.filter { j =>
          j("kind").str == "checkbox"
        }
        val bs = checkboxJsons.map { j =>
          val columnName = j("name").str
          val trueColumnName = getTrueColumnName(columnName)
          val vs = j("value").arr.map(_.str)
          val cV = x.value.getStrColumn(trueColumnName)
          columnName match {
            case "circBase" =>
              val bs = vs.map { v =>
                v match {
                  case "Yes" => cV =!= "NA"
                  case "No" => cV === "NA"
                }
              }
              bs.foldLeft(false.bind)((x, y) => x || y)
            case "geneSymbol" =>
              val bs = vs.map { v =>
                v match {
                  case "both" => true.bind
                  case "exist" => cV =!= "NA"
                  case "NA" => cV === "NA"
                }
              }
              bs.foldLeft(false.bind)((x, y) => x || y)
            case _ => cV.inSetBind(vs)
          }
        }
        val b = bs.foldLeft(true.bind)((x, y) => x && y)
        b
      }
      .filter { x =>
        val numJsons = searchJsons.filter { j =>
          j("kind").str == "int"
        }
        val bs = numJsons.map { j =>
          val columnName = j("name").str
          val trueColumnName = getTrueColumnName(columnName)
          val minVOp = j("min").numOpt
          val maxVOp = j("max").numOpt
          val cV = x.value.getIntColumn(trueColumnName).asColumnOf[Double]
          val maxB = maxVOp
            .map { maxV =>
              cV <= maxV
            }
            .getOrElse(columnTrue)
          val minB = minVOp
            .map { minV =>
              cV >= minV
            }
            .getOrElse(columnTrue)
          minB && maxB
        }
        val b = bs.foldLeft(true.bind)((x, y) => x && y)
        b
      }
      .filter { x =>
        val numJsons = searchJsons.filter { j =>
          j("kind").str == "double"
        }
        val bs = numJsons.map { j =>
          val columnName = j("name").str
          val trueColumnName = getTrueColumnName(columnName)
          val minVOp = j("min").numOpt
          println(minVOp)
          val maxVOp = j("max").numOpt
          val cV = x.value.getDoubleColumn(trueColumnName)
          val maxB = maxVOp
            .map { maxV =>
              cV <= maxV
            }
            .getOrElse(columnTrue)
          val minB = minVOp
            .map { minV =>
              cV >= minV
            }
            .getOrElse(columnTrue)
          minB && maxB
        }
        val b = bs.foldLeft(true.bind)((x, y) => x && y)
        b
      }
      .filter { x =>
        val allSearchText = map("allSearch").str
        val bs = table.baseTableRow.create_*.map { fs =>
          val columnName = fs.name
          fs.tpe.toString match {
            case v if v.startsWith("String") => x.getStrColumn(columnName).like(s"%${allSearchText}%")
            case v if v.startsWith("Int") => x.getIntColumn(columnName).asColumnOf[String].like(s"%${allSearchText}%")
            case v if v.startsWith("Double") =>
              x.getDoubleColumn(columnName).asColumnOf[String].like(s"%${allSearchText}%")
          }
        }
        val b = bs.reduceLeft((x, y) => x || y)
        b
      }

    val sortData = map
      .get("sort")
      .map { json =>
        val field = json("field").str
        val kind = json("kind").str
        val order = json("order").str
        val columnName = getTrueColumnName(field)
        kind match {
          case "int" =>
            order match {
              case "asc" =>
                filterData.sortBy { y =>
                  y.getIntColumn(columnName)
                }
              case "desc" =>
                filterData.sortBy { x =>
                  x.getIntColumn(columnName).desc
                }
            }
          case "double" =>
            order match {
              case "asc" =>
                filterData.sortBy { y =>
                  y.getDoubleColumn(columnName)
                }
              case "desc" =>
                filterData.sortBy { x =>
                  x.getDoubleColumn(columnName).desc
                }
            }
          case "str" =>
            order match {
              case "asc" =>
                filterData.sortBy { y =>
                  y.getStrColumn(columnName)
                }
              case "desc" =>
                filterData.sortBy { x =>
                  x.getStrColumn(columnName).desc
                }
            }
          case _ => filterData
        }
      }
      .getOrElse(filterData)

    val finalData = sortData
    val countF = finalData.length.result
    val resultF = finalData.drop(pageData.offset).take(pageData.limit).result
    val f = countF.zip(resultF)
    db.run(f)
  }

}
